package com.jphenix.service.db.util;

import java.sql.Clob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.jphenix.driver.threadpool.ThreadSession;
import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.service.db.common.instancea.DBQuery;
import com.jphenix.service.db.exception.DBException;
import com.jphenix.share.lang.SListMap;
import com.jphenix.standard.db.IDBQuery;
import com.jphenix.standard.db.IDataDict;
import com.jphenix.standard.docs.ClassInfo;

/**
 * 按游标返回指向的记录集处理类
 * 通常用于查询处理超大数据表中所有的数据
 * 之前是分页获取一批数据进行处理，但这样做在随着页号越来越大，每次分页查询的速度也就越来越慢
 * 如果改用该类来处理每条数据，就不存在重复分页查询，而是采用记录集游标指向记录的方式，
 * 既不会造成加载数据量过大导致内存溢出，也不会反复分页查询
 * 
 * 创建该类的目的是封装操作游标，防止忘记关闭数据库连接、事务、游标，导致整个系统不稳定
 * 
 * @author 刘虻
 * 2021-01-22 13:29:41
 */
@ClassInfo({"2021-03-02 13:16","按游标返回指向的记录集处理类"})
public class DbResult extends ABase {
  
  private DBQuery           dbq    = null; // 数据库操作类
  private ResultSet         rs     = null; // 记录集
  private PreparedStatement stmt   = null; // 事务
  private Connection        conn   = null; // 连接对象
  private List<String>      fields = null; // 需要查询的字段名序列
  private Object[]          dicts  = null; // 字典类实例数组
  private long              count  = 0;    // 读取记录数

  /**
   * 构造函数
   * @param dbq 数据库操作类
   */
  public DbResult(IDBQuery dbq){
    super();
    this.dbq = (DBQuery)dbq;
    setBase(dbq); //设置基类
  }

  /**
   * 执行查询（不带拼装值）
   * @param sql          查询语句
   * @throws DBException 异常
   */
  public void query(String sql) throws DBException {
    query(null,sql,null);
  }

  /**
   * 执行查询
   * @param sql          查询语句
   * @param uList        语句中需要拼装的值序列
   * @throws DBException 异常
   */
  public void query(String sql,List<String> uList) throws DBException {
    query(null,sql,uList);
  }

  /**
   * 执行查询（传入组合语句信息，在脚本中通过 <%S  S%> 得来
   * @param infos          组合查询信息
   * @throws DBException   遗产
   */
  public void query(Object[] infos) throws DBException {
    query(infos,null,null);
  }

  /**
   * 返回当前读取了多少条记录
   * @return 当前读取了多少条记录
   */
  public long count(){
    return count;
  }

  /**
   * 获取当前行记录，并将游标指针指向下一条。如果到达记录末尾或者没有数据，则返回空
   * @return              游标指向的行记录数据
   * @throws DBException  异常
   */
  @SuppressWarnings({ "unchecked" })
  public Map<String,String> get() throws DBException {
    Map<String,String> resMap = null; //构建返回值
    try{
      boolean hasData = rs.next(); //游标指向下一条
      if(!hasData || fields==null){
        close();
        //没有数据
        return null;
      }
      count++;
      Object columnValue; //字段值
      //构建返回值
      resMap = new HashMap<>();
      for (String columnName:fields) {
        //获取字段值
        columnValue = rs.getObject(columnName);
        if (columnValue instanceof Clob) {
          resMap.put(columnName,dbq.getClobString(((Clob)columnValue)));
        }else if(columnValue instanceof byte[]) {
          //mysql 在语句中使用函数时，有时会将本应该返回字符串的值返回字节数组
          resMap.put(columnName,new String((byte[])columnValue));
        }else {
          if(columnValue==null){
            columnValue = "";
          }
          resMap.put(columnName,columnValue.toString());
        }
      }
      if(dicts!=null) {
        for(int i=0;i<dicts.length;i++) {
          if(dicts[i] instanceof IDataDict) {
            ((IDataDict)dicts[i]).data(resMap);
          }else if(dicts[i] instanceof SListMap) {
            //强制转换为序列容器
            SListMap<Map<String,String>> dict = (SListMap<Map<String,String>>)dicts[i];
            Map<String,String> dMap;
            for(String fEle:dict.keys()) {
              dMap = dict.get(fEle);
              if(dMap==null) {
                continue;
              }
              resMap.put(fEle,dMap.get(resMap.get(fEle)));
            }
          }
        }
      }
    }catch(Exception e){
      e.printStackTrace();
      throw new DBException(this,e.toString());
    }
    return resMap;
  }

  /**
   * 设置字典实例数组
   * @param dicts 字典实例数组
   */
  public void setDicts(Object[] dicts){
    this.dicts = dicts;
    //初始化字典类实例
    if(dicts!=null) {
      for(int i=0;i<dicts.length;i++) {
        if(dicts[i] instanceof IDataDict) {
          try{
            ((IDataDict)dicts[i]).init();
          }catch(Exception e){
            e.printStackTrace();
          }
        }
      }
    }
  }

  /**
   * 关闭连接资源
   */
  public void close(){
    count = 0;
    if(rs!=null){
      try{
        rs.close();
      }catch(Exception e){}
      rs = null;
    }
    if(stmt!=null){
      try{
        stmt.close();
      }catch(Exception e){}
      stmt = null;
    }
    if(conn!=null){
      try{
        conn.close();
      }catch(Exception e){}
      conn = null;
    }
    if(fields!=null){
      fields.clear();
    }
    fields = null;
  }

  /**
   * 执行查询
   * @param infos         查询信息组合对象
   * @param sql           查询语句
   * @param uList         查询语句对应的传入值
   * @throws DBException  异常
   */
  @SuppressWarnings("unchecked")
  private void query(Object[] infos,String sql,List<String> uList) throws DBException {
      //在脚本中采用 <%S   S%> 拼装的信息
      if(infos!=null){
        Object[] fixInfos = dbq.fixSqlInfo(infos); //整理传入值
        sql            = (String)fixInfos[0];
        uList          = (List<String>)fixInfos[1];
      }
      close(); //尝试关闭之前的资源
      try{
        conn = dbq.getConn(null);
        //获取事务
        int pType = dbq.getPrepareStatementType();
        if(pType==0){
          stmt = conn.prepareStatement(sql);
        }else{
          stmt = 
            conn
              .prepareStatement(
                  sql
                  ,ResultSet.TYPE_SCROLL_INSENSITIVE
                  ,ResultSet.CONCUR_READ_ONLY);
        }
        //设置条件值
			  String paramsLog =  dbq.fixPreparedStatement(stmt,uList); // 设置值
        log.sqlLog("DataSource:["+dbq.getSourceName()+"] Begin QueryResult:\n"+sql+"\n"+paramsLog);
  
        //查询超时时间（秒）
        int timeOut = sint(ThreadSession.get("DB_QUERY_TIME_OUT"));
        if(timeOut>0) {
          log.sqlLog("----------setQueryTimeout("+timeOut+")");
          stmt.setQueryTimeout(timeOut);
        }
        rs = stmt.executeQuery();// 执行查询
			  ResultSetMetaData metaData = null; //获取字段信息
        try {
          //调用存储过程后没有返回记录集，此动作会抛异常
          metaData = rs.getMetaData();
        }catch(Exception e) {}
        if(metaData!=null){
          fields = new ArrayList<>();
          //获取字段数量
          int    colCountInt = metaData.getColumnCount();
          String fieldName; //字段名元素
          for(int i=0;i<colCountInt;i++) {
            fieldName = str(metaData.getColumnLabel(i+1)).toLowerCase();
            if(fieldName.length()<1) {
              fieldName = str(metaData.getColumnName(i+1)).toLowerCase();
            }
            fields.add(fieldName);
          }
        }
      }catch(Exception e){
        e.printStackTrace();
        throw new DBException(this,e.toString());
      }
  }

  /**
   * 类销毁时执行（不需要外部调用）
   */
  protected void finalize() throws Throwable {
    close(); //关闭所有链接资源
    super.finalize();
  }
}
